package com.zj.filters;

import com.alibaba.nacos.shaded.com.google.gson.Gson;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.Data;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@Log
public class GlobalTokenfilter implements GlobalFilter, Ordered {
    @Value("${token.secret}")
    private String secret;
    @Value("${token.expiration}")
    private long tokenExpiration;
    private final AppProperties appProperties;
    //路径匹配器
    private final AntPathMatcher antPathMatcher=new AntPathMatcher();//路径匹配器
    private final Gson gson=new Gson();
//构造方法，因为使用了final
    public GlobalTokenfilter(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.先取出路径
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();
//        //1.1 去掉第一个路径段
//        path = removeFirstPathSegment(path);
//        log.info("请求路径：" + path);
        //2.判断路径是否需要校验
        boolean isToCheckPath = false;
        for (String checkTokenPath : appProperties.getCheckTokenPaths()) {
            if (antPathMatcher.match(checkTokenPath, path)) {  //访问路径path是否为要检查token的路径
                isToCheckPath = true;
                break;
            }
        }
        //3.如果不需要校验，直接放行
        if (!isToCheckPath) {
            log.info("不需要校验token的路径: " + path);
            return  chain.filter(   exchange);   //放行
        }
        //4.如果需要校验，取出token
        String token = request.getHeaders().getFirst("token"); //在请求头中取 header 中的 token
        if (StringUtils.isEmpty(token)) {
            //5.如果没有token，返回错误信息
            log.warning("没有token" + path + "这个请求没有被认证过");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "未登录或登录信息缺失");
            return writeResponse(response, map); //
        }
        //6.如果有token，校验token
        try {
            Key signKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signKey).build().parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            Date expiration = claims.getExpiration();
            Date now = new Date();
            if (expiration != null && expiration.before(now)) {
                //7.如果token过期，返回错误信息
                log.warning("token过期" + path);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                Map<String, Object> map = new HashMap<>();
                map.put("code", 0);
                map.put("msg", "登录信息过期");
                return writeResponse(response, map); //
            }
            //8.如果token没有过期，放行,从token 中解析出来的信息如下：
            log.info("Token有效路径" + path);
            log.info("用户名" + claims.get("userName"));
            log.info("用户id" + claims.get("userId"));
            log.info("用户角色" + claims.get("roles"));
            log.info("过期时间" + expiration);
            log.info("产生时间" + claims.getIssuedAt());

            ServerHttpRequest.Builder builder = request.mutate();
            // 从Claims中提取用户信息
            Long userId = claims.get("userId", Long.class); // 从Claims中提取用户ID
            String username = claims.get("userName", String.class); // 从Claims中提取用户名
            Object rolesObj = claims.get("roles"); // 从Claims中提取角色信息
            String roles = null;
            if (rolesObj instanceof List) {
                roles = String.join(",", (List<String>) rolesObj);
            } else if (rolesObj != null) {
                roles = rolesObj.toString();
            }
            if (roles != null && !roles.isEmpty()) {
                builder.header("X-MyUser-Roles", roles);
            }

            if (userId != null) {
                builder.header("X-MyUser-ID", userId.toString());
            }
            if (username != null) {
                builder.header("X-MyUser-Name", username);
            }
            ServerHttpRequest newRequest = builder.build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange); // Proceed with the modified request
        } catch (ExpiredJwtException e) {
            // This catch block is technically redundant if `if (expiration != null && expiration.before(now))` handles it.
            // But it's good for explicit handling if the `parseClaimsJws` itself throws it directly.
            log.severe("Token 已过期 (ExpiredJwtException): " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "token已过期，请重新登录");
            return writeResponse(response, map);
        } catch (JwtException e) {
            e.printStackTrace();
            log.severe("Token 非法！(JwtException): " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.FORBIDDEN); // Set HTTP status to 403
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "token无效或非法");
            return writeResponse(response, map);
        } catch (Exception e) {
            // Catch any other unexpected exceptions during token processing
            log.severe("处理Token时发生未知错误: " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); // 500 Internal Server Error
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "服务器内部错误，请稍后再试");
            return writeResponse(response, map);
        }

    }

    private Mono<Void> writeResponse(ServerHttpResponse response, Object data) {
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        String json = gson.toJson(data);
        DataBuffer dataBuffer = response.bufferFactory().wrap(json.getBytes());
        return response.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; //这是一个高优先级，保证当前filter优先于其他filter
    }

//    //路径
//    public  String removeFirstPathSegment(String path) {
//        if (path == null || path.isEmpty()) {
//            return path;
//        }
//
//        // 使用正则表达式替换第一个路径段
//        return path.replaceFirst("^/[^/]+", "");
//    }
}
@Component
@ConfigurationProperties(prefix="right")
@Data
class AppProperties {
    private List<String> checkTokenPaths;
}
